<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Carver</title>
    <style>
      * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
      }
      .wrapper {
        width: 80vw;
        margin: 0 auto;
        overflow-y: auto;
      }
      .title {
        text-align: center;
        padding: 10px;
      }
      .tool {
        min-height: 50px;
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        gap: 20px;
        padding: 10px 0;
        margin-bottom: 20px;
      }
      .tool button {
        padding: 6px 14px;
        cursor: pointer;
      }
      #carver {
        width: 100%;
        max-height: calc(100vh - 300px);
      }
      #dialog {
        width: 100%;
        height: 100%;
        position: fixed;
        top: 0;
        left: 0;
        background-color: rgba(0, 0, 0, 0.2);
        flex-direction: column;
        display: none;
        justify-content: center;
        align-items: center;
      }
      #dialog_header {
        width: 500px;
        height: 30px;
        background-color: #fff;
        display: flex;
        align-items: center;
        padding: 0 10px;
        font-weight: bold;
      }
      #dialog_body {
        width: 500px;
        background-color: #fff;
        padding: 20px;
      }
      .dialog-row {
        display: flex;
        align-items: center;
      }
      .dialog-row .title {
        font-weight: 300;
        white-space: nowrap;
      }
      .dialog-row + .dialog-row {
        margin-top: 20px;
      }
      .dialog-row input {
        height: 30px;
        width: 300px;
        padding: 0 5px;
      }
      #dialog_footer {
        width: 500px;
        height: 50px;
        background-color: #fff;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0 10px;
      }
      #dialog_footer button {
        height: 30px;
        padding: 0 20px;
        cursor: pointer;
      }
      #dialog_footer button + button {
        margin-left: 20px;
      }
      .tool-menu {
        width: fit-content;
        height: fit-content;
        box-shadow: 5px 5px 8px rgb(104, 104, 104);
        width: 100px;
        padding: 5px 0;
        background-color: #fff;
      }
      .tool-menu-item {
        height: 30px;
        padding: 0 10px;
        line-height: 30px;
        user-select: none;
        cursor: pointer;
        font-size: 12px;
      }
      .tool-menu-item:hover {
        background-color: dodgerblue;
        color: white;
      }
    </style>
  </head>
  <body>
    <div class="wrapper">
      <div class="title">
        <h3>Carver 划词工具演示</h3>
      </div>
      <div class="tool">
        操作：
        <button id="select_button">单个划词</button>
        <button id="multi_select_button">批量划词</button>
        <button id="multi_revoke_button" style="display: none">撤销</button>
        <button id="multi_collect_button">收集标签</button>
        <button id="multi_endCollect_button">结束收集</button>
        <button id="multi_cancelCollect_button">取消收集</button>
        <button id="multi_resetText_button">更新文本</button>
      </div>
      <div id="carver"></div>
      <div id="dialog">
        <div id="dialog_header">添加标签</div>
        <div id="dialog_body">
          <div class="dialog-row">
            <span class="title">文本内容：</span><span id="dialog_text"></span>
          </div>
          <div class="dialog-row">
            <span class="title">标签内容：</span>
            <input id="dialog_input" placeholder="请输入" />
          </div>
          <div class="dialog-row">
            <span class="title">标签颜色：</span>
            <input type="color" id="dialog_color" />
          </div>
        </div>
        <div id="dialog_footer">
          <button id="dialog_cancel">取消</button>
          <button id="dialog_confirm">确认</button>
        </div>
      </div>
    </div>
    <script src="./carver.js"></script>
    <script>
      const Mock =
        "一、JavaScript简介\nJavaScript（简称“JS”） 是一种具有函数优先的轻量级，解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名，但是它也被用到了很多非浏览器环境中，JavaScript 基于原型编程、多范式的动态脚本语言，并且支持面向对象、命令式、声明式、函数式编程范式。\nJavaScript在1995年由Netscape公司的Brendan Eich，在网景导航者浏览器上首次设计实现而成。因为Netscape与Sun合作，Netscape管理层希望它外观看起来像Java，因此取名为JavaScript。但实际上它的语法风格与Self及Scheme较为接近。\nJavaScript的标准是ECMAScript 。截至 2012 年，所有浏览器都完整的支持ECMAScript 5.1，旧版本的浏览器至少支持ECMAScript 3 标准。2015年6月17日，ECMA国际组织发布了ECMAScript的第六版，该版本正式名称为 ECMAScript 2015，但通常被称为ECMAScript 6 或者ES2015。\n\n二、产生背景\nJavaScript最初由Netscape的Brendan Eich设计，最初将其脚本语言命名为LiveScript，后来Netscape在与Sun合作之后将其改名为JavaScript。JavaScript最初受Java启发而开始设计的，目的之一就是“看上去像Java”，因此语法上有类似之处，一些名称和命名规范也借自Java，但JavaScript的主要设计原则源自Self和Scheme。JavaScript与Java名称上的近似，是当时Netscape为了营销考虑与Sun微系统达成协议的结果。微软同时期也推出了JScript来迎战JavaScript的脚本语言。\n发展初期，JavaScript的标准并未确定，同期有Netscape的JavaScript，微软的JScript和CEnvi的ScriptEase三足鼎立。为了互用性，Ecma国际（前身为欧洲计算机制造商协会）创建了ECMA-262标准（ECMAScript），两者都属于ECMAScript的实现，尽管JavaScript作为给非程序人员的脚本语言，而非作为给程序人员的脚本语言来推广和宣传，但是JavaScript具有非常丰富的特性。 [10]  1997年，在ECMA（欧洲计算机制造商协会）的协调下，由Netscape、Sun、微软、Borland组成的工作组确定统一标准：ECMA-262。完整的JavaScript实现包含三个部分：ECMAScript，文档对象模型，浏览器对象模型。\nJavaScript是甲骨文公司的注册商标。Ecma国际以JavaScript为基础制定了ECMAScript标准。JavaScript也可以用于其他场合，如服务器端编程（Node.js）。\n\n三、主要功能\n1.嵌入动态文本于HTML页面。\n2.对浏览器事件做出响应。\n3.读写HTML元素。\n4.在数据被提交到服务器之前验证数据。\n5.检测访客的浏览器信息，控制cookies，包括创建和修改等。\n6.基于Node.js技术进行服务器端编程。\n\n四、语言组成\nECMAScript，描述了该语言的语法和基本对象。\n文档对象模型（DOM），描述处理网页内容的方法和接口。\n浏览器对象模型（BOM），描述与浏览器进行交互的方法和接口。\n\n五、运行模式\nJavaScript是一种属于网络的高级脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。\n是一种解释性脚本语言（代码不进行预编译）。\n主要用来向HTML（标准通用标记语言下的一个应用）页面添加交互行为。\n可以直接嵌入HTML页面，但写成单独的js文件有利于结构和行为的分离。\n跨平台特性，在绝大多数浏览器的支持下，可以在多种平台下运行（如Windows、Linux、Mac、Android、iOS等）。\nJavaScript脚本语言同其他语言一样，有它自身的基本数据类型，表达式和算术运算符及程序的基本程序框架。JavaScript提供了四种基本的数据类型和两种特殊数据类型用来处理数据和文字。而变量提供存放信息的地方，表达式则可以完成较复杂的信息处理。\n\n六、语言特点\nJavaScript脚本语言具有以下特点\n(1)脚本语言。JavaScript是一种解释型的脚本语言，C、C++等语言先编译后执行，而JavaScript是在程序的运行过程中逐行进行解释。\n(1)脚本语言。JavaScript是一种解释型的脚本语言，C、C++等语言先编译后执行，而JavaScript是在程序的运行过程中逐行进行解释。\n(3)简单。JavaScript语言中采用的是弱类型的变量类型，对使用的数据类型未做出严格的要求，是基于Java基本语句和控制的脚本语言，其设计简单紧凑。\n(4)动态性。JavaScript是一种采用事件驱动的脚本语言，它不需要经过Web服务器就可以对用户的输入做出响应。在访问一个网页时，鼠标在网页中进行鼠标点击或上下移、窗口移动等操作JavaScript都可直接对这些事件给出相应的响应。\n(5)跨平台性。JavaScript脚本语言不依赖于操作系统，仅需要浏览器的支持。因此一个JavaScript脚本在编写后可以带到任意机器上使用，前提是机器上的浏览器支 持JavaScript脚本语言，JavaScript已被大多数的浏览器所支持。 [6]  不同于服务器端脚本语言，例如PHP与ASP，JavaScript主要被作为客户端脚本语言在用户的浏览器上运行，不需要服务器的支持。所以在早期程序员比较青睐于JavaScript以减少对服务器的负担，而与此同时也带来另一个问题，安全性。\n而随着服务器的强壮，虽然程序员更喜欢运行于服务端的脚本以保证安全，但JavaScript仍然以其跨平台、容易上手等优势大行其道。同时，有些特殊功能（如AJAX）必须依赖JavaScript在客户端进行支持。"
      const Mook1 =
        "一、JavaScript简介\nJavaScript（简称“JS”） 是一种具有函数优先的轻量级，解释型或即时编译型的编程语言。"
      const carver = new Carver({
        root: document.getElementById("carver"),
        style: {
          // 为标签指定默认样式
          label: {
            backgroundColor: "#048901",
            color: "#f6f6f6"
          }
        },
        config: {
          letterSpacing: 20
        }
      })
      carver.text = Mock

      // carver.onLabelClick = (vlabel, e) => {
      // carver.connectByExData(vlabel.exData).then((res) => {
      //   carver.addPath({
      //     startLabel: res.startLabel,
      //     endLabel: res.endLabel,
      //     textContent: new Date().getTime(),
      //   });
      // });
      // carver.removeLabel({ id: target.id });
      // if (flag) {
      //   labelDialog(target.textContent, "connect").then(
      //     ({ value, color }) => {
      //       carver.labelClickToLineTwo(target, value); // 第二次点击label
      //     }
      //   );
      //   return;
      // }
      // toolMenu(e.clientX, e.clientY, target.type)
      //   .then((type) => {
      //     if (type === "del") {
      //       carver.removeLabel(target).then((res) => {
      //         console.log("已删除的标签: ", res);
      //       });
      //     }
      //     if (type === "edit") {
      //       labelDialog(target.textContent, type).then(({ value, color }) => {
      //         carver
      //           .editLabel(target, value, { backgroundColor: color })
      //           .then((res) => {
      //             console.log("修改后的标签: ", res);
      //           });
      //       });
      //     }
      //     if (type === "connect") {
      //       if (!flag) {
      //         carver.labelClickToLineOne(target); // 第一次点击选择操作之后
      //       }
      //     }
      //   })
      //   .catch((type) => {});
      // };
      carver.onPathClick = (target, e) => {
        console.log(carver)
        console.log(target)
      }
      carver.onLabelClick = (target, e) => {
        console.log(carver)
        console.log(target)
      }
      // carver.onPathClick = (target, e) => {
      //   toolMenu(e.clientX, e.clientY, target.type)
      //     .then((type) => {
      //       if (type === "del") {
      //         carver.removePath(target);
      //       }
      //       if (type === "edit") {
      //         labelDialog(target.textContent).then(({ value, color }) => {
      //           carver.editPath(target, value, { backgroundColor: color });
      //         });
      //       }
      //     })
      //     .catch((type) => {});
      // };

      carver.addLabel([
        {
          startIndex: 0,
          endIndex: 3,
          textContent: "标签1",
          exData: 2
        },
        {
          startIndex: 15,
          endIndex: 20,
          textContent: "重复的标签"
        },
        {
          startIndex: 56,
          endIndex: 60,
          textContent: Mock.slice(56, 60) + "2",
          exData: 0
        },
        {
          startIndex: 37,
          endIndex: 42,
          textContent: Mock.slice(37, 42) + "3"
        },
        {
          startIndex: 31,
          endIndex: 33,
          textContent: Mock.slice(31, 33) + "4"
        },
        {
          startIndex: 90,
          endIndex: 98,
          textContent: Mock.slice(90, 98) + "5"
        },
        {
          startIndex: 100,
          endIndex: 106,
          textContent: Mock.slice(90, 98) + "6"
        },
        {
          startIndex: 110,
          endIndex: 115,
          textContent: Mock.slice(90, 98) + "7",
          exData: 3
        },
        {
          startIndex: 130,
          endIndex: 138,
          textContent: Mock.slice(90, 98) + "8",
          exData: 1
        },
        {
          startIndex: 2567,
          endIndex: 2569,
          textContent: Mock.slice(2567, 2569) + "9",
          exData: 999
        }
      ])

      carver.editLabel(
        {
          startIndex: 0,
          endIndex: 3
        },
        "编辑",
        {
          backgroundColor: "red"
        }
      )

      carver
        .addPathByExData({
          startLabelExData: 0,
          endLabelExData: 999,
          textContent: "连线1",
          exData: 22
        })
        .then((res) => {})
      carver
        .addPathByExData({
          startLabelExData: 0,
          endLabelExData: 999,
          textContent: "连线",
          exData: 23
        })
        .then((res) => {})

      carver.editPathByExData(23, "hhhhh", {
        heightLightColor: "#000",
        borderColor: "red"
      })

      carver
        .addMarkByIndex({
          fromIndex: 67,
          toIndex: 80,
          // labelId: vlabel.id,
          style: {
            backgroundColor: "red",
            opacity: 0.5
          }
        })
        .then((res) => {
          console.log(res)
        })

      carver
        .addMarkByIndex({
          fromIndex: 169,
          toIndex: 180,
          // labelId: vlabel.id,
          style: {
            backgroundColor: "green",
            opacity: 0.5
          }
        })
        .catch((res) => {
          console.log(res)
        })

      // carver.editPathByExData(11, "llll", {
      //   heightLightColor: "#000",
      //   borderColor: "orange",
      // });

      // 单个划词按钮
      const select_button = document.getElementById("select_button")
      // 批量划词按钮
      const multi_select_button = document.getElementById("multi_select_button")
      // 批量划词撤销按钮
      const multi_revoke_button = document.getElementById("multi_revoke_button")
      // 收集标签
      const multi_collect_button = document.getElementById(
        "multi_collect_button"
      )
      // 结束收集
      const multi_endCollect_button = document.getElementById(
        "multi_endCollect_button"
      )
      // 取消收集
      const multi_cancelCollect_button = document.getElementById(
        "multi_cancelCollect_button"
      )
      // 更新文本
      const multi_resetText_button = document.getElementById(
        "multi_resetText_button"
      )

      // 收集标签
      multi_collect_button.onclick = () => {
        carver
          .collectLabel()
          .then((res) => {
            console.log("收集成功", res)
            let arr = []
            res.endLabel.forEach((item) => {
              arr.push({
                startLabel: res.startLabel,
                endLabel: item,
                textContent: item.textContent
              })
            })
            carver.addPath(arr)
          })
          .catch((err) => {
            console.log("取消收集", err)
          })
      }
      // 结束收集标签
      multi_endCollect_button.onclick = () => {
        carver.endCollectLabel()
      }
      // 取消收集标签
      multi_cancelCollect_button.onclick = () => {
        carver.cancelCollectLabel()
      }

      multi_resetText_button.onclick = () => {
        carver.text = Mook1
        console.log(carver)
        carver.addLabel([
          {
            startIndex: 0,
            endIndex: 3,
            textContent: "标签1",
            exData: 2
          },
          {
            startIndex: 15,
            endIndex: 20,
            textContent: "重复的标签"
          },
          {
            startIndex: 56,
            endIndex: 60,
            textContent: Mock.slice(56, 60) + "2",
            exData: 0
          }
        ])
      }
      // carver.highlightLabelByExData(999);

      // 单个划词
      let select_flag = false
      function set_select_flag(flag) {
        select_flag = flag
        if (flag) {
          select_button.innerText = "取消划词"
          return
        }
        select_button.innerText = "单个划词"
      }
      select_button.onclick = () => {
        if (select_flag) {
          carver.cancelSelect()
          set_select_flag(false)
          return
        }
        if (!select_flag) {
          set_select_flag(true)
          carver
            .select()
            .then((res) => {
              console.log("单个划词结束：", res)
              labelDialog(res.text)
                .then(({ value, color }) => {
                  carver.addLabel({
                    startIndex: res.fromIndex,
                    endIndex: res.toIndex,
                    textContent: value,
                    style: {
                      backgroundColor: color
                    }
                  })
                })
                .catch(() => {
                  // 添加标签取消
                  carver.cancelSelect()
                })
            })
            .catch((res) => {
              console.log("单个划词取消：", res)
            })
            .finally(() => {
              set_select_flag(false)
            })
        }
      }

      // 批量划词
      let multi_select_flag = false
      function set_multi_select_flag(flag) {
        multi_select_flag = flag
        if (flag) {
          multi_select_button.innerText = "结束划词"
          multi_revoke_button.style.display = ""
          return
        }
        multi_select_button.innerText = "批量划词"
        multi_revoke_button.style.display = "none"
      }
      multi_select_button.onclick = () => {
        if (multi_select_flag) {
          carver.cancelSelect()
          set_multi_select_flag(false)
          return
        }
        if (!multi_select_flag) {
          set_multi_select_flag(true)
          carver
            .select(true, (e) => {
              console.log(`第${e.eventIndex}个词组：`, e)
            })
            .catch((res) => {
              console.log("批量划词结束：", res)
              if (!res.length) {
                return
              }
              const val = res.map(({ text }) => text).join("\n")
              labelDialog(val)
                .then(({ value, color }) => {
                  carver.addLabel(
                    res.map(({ fromIndex, toIndex }) => {
                      return {
                        startIndex: fromIndex,
                        endIndex: toIndex,
                        textContent: value,
                        style: {
                          backgroundColor: color
                        }
                      }
                    })
                  )
                })
                .catch(() => {
                  // 添加标签取消
                })
            })
            .finally(() => {
              set_multi_select_flag(false)
            })
        }
      }
      multi_revoke_button.onclick = () => {
        const { deleted, current } = carver.revoke()
        console.log("deleted：", deleted, "current: ", current)
      }

      // 弹窗
      function labelDialog(text, type) {
        const dialog = document.getElementById("dialog")
        const dialog_header = document.getElementById("dialog_header")
        const dialog_text = document.getElementById("dialog_text")
        const dialog_input = document.getElementById("dialog_input")
        const dialog_color = document.getElementById("dialog_color")
        const dialog_cancel = document.getElementById("dialog_cancel")
        const dialog_confirm = document.getElementById("dialog_confirm")
        if (type === "connect") {
          dialog_header.innerText = "添加路径"
        }
        if (type === "edit") {
          dialog_header.innerText = "添加标签"
        }
        dialog_text.innerText = text
        dialog_input.value = ""
        dialog_color.value = ""
        dialog.style.display = "flex"
        return new Promise((resolve, reject) => {
          dialog_cancel.onclick = () => {
            dialog.style.display = "none"
            reject("cancel")
          }
          dialog_confirm.onclick = () => {
            dialog.style.display = "none"
            resolve({
              value: dialog_input.value,
              color: dialog_color.value
            })
          }
        })
      }

      // 工具菜单
      function toolMenu(x, y, type) {
        const id = "tool_menu"
        let tool_menu = document.getElementById(id)
        if (!tool_menu) {
          tool_menu = document.createElement("div")
          tool_menu.id = id
          tool_menu.style.display = "none"
          tool_menu.className = "tool-menu"
          tool_menu.style.position = "absolute"
          tool_menu.style.top = 0
          tool_menu.style.left = 0

          const edit = document.createElement("div")
          edit.className = "tool-menu-item"
          edit.innerText = "编辑"

          const del = document.createElement("div")
          del.className = "tool-menu-item"
          del.innerText = "删除"

          const cancel = document.createElement("div")
          cancel.className = "tool-menu-item"
          cancel.innerText = "取消"

          tool_menu.appendChild(edit)
          tool_menu.appendChild(del)
          tool_menu.appendChild(cancel)

          document.body.appendChild(tool_menu)
        }
        if (tool_menu) {
          if (type === "path") {
            if (tool_menu.children.length === 4) {
              tool_menu.removeChild(tool_menu.children[2])
            }
          }
          if (type === "label") {
            if (tool_menu.children.length === 3) {
              const connect = document.createElement("div")
              connect.className = "tool-menu-item"
              connect.innerText = "连线"
              tool_menu.insertBefore(connect, tool_menu.lastElementChild)
            }
          }
        }

        tool_menu.style.display = ""
        tool_menu.style.transform = `translate(${x}px, ${y}px)`

        return new Promise((resolve, reject) => {
          // 事件冒泡机制
          tool_menu.addEventListener("click", (e) => {
            if (e.target.innerText === "取消") {
              reject("cancel")
            }
            if (e.target.innerText === "编辑") {
              resolve("edit")
            }
            if (e.target.innerText === "删除") {
              resolve("del")
            }
            if (e.target.innerText === "连线") {
              resolve("connect")
            }
            tool_menu.style.display = "none"
          })
        })
      }
    </script>
  </body>
</html>
